#include <unabto_app.h>
#include "application.h"
#include "hal.h"
#include <unabto_stream.h>

uint8_t TURNED = 0;
uint8_t DETECTION = 0;

#if STREAM_DEMO == 1
/**
 * The taks that handles incoming streams.
 */
void streamLoop(void);
#endif

#if ACL_DEMO == 1

#define ACL_MAXIMUM_USERS                                                       4 // the maximum number of users in the ACL
#define ACL_MAXIMUM_USERNAME_LENGTH                                             50 // the maximum length of client ids

/**
 * Buffer for holding the current client id in the ACL add/remove requests. Too big to be placed on the stack.
 */
static char currentClientId[ACL_MAXIMUM_USERNAME_LENGTH + 1];

/**
 * Initializes the ACL store.
 */
void acl_initialize(void);

/**
 * Attempts to find the user with the specified client id in the ACL.
 * @param clientId The client id of the user to remove.
 * @return Returns true if the user was in the ACL.
 */
bool acl_lookup(const char* clientId);

/**
 * Adds a user to the ACL.
 * @param clientId The client id of the user to remove.
 * @return Returns true if the user was added to the ACL or if the user was already in the ACL.
 */
bool acl_add(const char* clientId);

/**
 * Removes a user from the ACL.
 * @param clientId The client id of the user to remove.
 * @return Returns true if the user was removed from the ACL.
 * */
bool acl_remove(const char* clientId);


bool allow_client_access(const char* client_id) {
    return acl_lookup(client_id);
}

#endif

/*void test_temp_read(void)
{
    uint32_t temperature = read_temperature();

    if(temperature > HIGH_TEMPERATURE)
    {
        toggle_led();
    }
}*/

void loop(void)
{
    if(TURNED == 1)
    {        
        uint32_t motion = read_analog_input(3);

        if(motion > 100)
        {
            toggle_led();
            DETECTION = 1;
            set_pwm(2, true, 175);
            io_config(10, 1);
        }
        else
        {
            DETECTION = 0;
            //set_pwm(2, false, 0);
            io_config(10, 0);
        }
    }
    if(TURNED == 0)
    {
       DETECTION = 0;
       LATEbits.LATE0 = 0;
       set_pwm(2, false, 0);
       io_config(10, 0);
    }
}

application_event_result_t application_event(application_request_t* request, buffer_read_t* read_buffer, buffer_write_t* write_buffer) {
    switch (request->query_id) {
        case 1:
        {
            //  <query name="button_status" description="Button" id="1">
            //    <request>
            //    </request>
            //    <response>
            //      <parameter name="button_status" type="uint8"/>
            //    </response>
            //  </query>

            uint8_t button_status = read_button_status();

            if (!buffer_write_uint8(write_buffer, button_status)) return AER_REQ_RSP_TOO_LARGE;

            return AER_REQ_RESPONSE_READY;
        }

        case 2:
        {
            //  <query name="led" description="LED" id="2">
            //    <request>
            //		<parameter name="led_ids" type="uint8"/>
            //	</request>
            //    <response>
            //		<parameter name="led_status" type="uint8"/>
            //    </response>
            //  </query>

            uint8_t led_ids;
            uint8_t led_status;

            if (!buffer_read_uint8(read_buffer, &led_ids)) return AER_REQ_TOO_SMALL; // parameter ignored for now

            // toggle led
            //led_status = toggle_led();

            // status
            if (!buffer_write_uint8(write_buffer, led_status)) return AER_REQ_RSP_TOO_LARGE;

            return AER_REQ_RESPONSE_READY;
        }

        case 3:
        {
            //  <query name="digital_io" description="Digital I/O" id="3">
            //    <request>
            //      <parameter name="io_index" type="uint8"/>
            //	  <parameter name="io_out" type="uint8"/>
            //    </request>
            //    <response>
            //      <parameter name="io_status" type="uint8"/>
            //    </response>
            //  </query>

            uint8_t io_index;
            uint8_t io_out;
            uint8_t io_status;

            if (!buffer_read_uint8(read_buffer, &io_index)) return AER_REQ_TOO_SMALL;
            if (!buffer_read_uint8(read_buffer, &io_out)) return AER_REQ_TOO_SMALL;

            // io configuration
            io_status = io_config(io_index, io_out);

            // status
            if (!buffer_write_uint8(write_buffer, io_status)) return AER_REQ_RSP_TOO_LARGE;

            return AER_REQ_RESPONSE_READY;
        }

        case 5:
        {
            //  <query name="temperature" description="Temp. sensor" id="5">
            //    <request>
            //    </request>
            //    <response>
            //      <parameter name="temperature_celcius" type="uint32"/>
            //    </response>
            //  </query>

            uint32_t temperature = read_temperature();

            //test_temp_read();

            if (!buffer_write_uint32(write_buffer, temperature)) return AER_REQ_RSP_TOO_LARGE;

            return AER_REQ_RESPONSE_READY;
        }

        case 6:
        {
            //  <query name="analog" description="Analog inputs" id="6">
            //    <request>
            //	  <parameter name="ch_index" type="uint8"/>
            //    </request>
            //    <response>
            //      <parameter name="analog_ch" type="uint32"/>
            //    </response>
            //  </query>

            uint8_t ch_index;
            uint32_t analog_ch = 0;
            uint8_t i;

            // read analog channel number
            if (!buffer_read_uint8(read_buffer, &ch_index)) return AER_REQ_TOO_SMALL;

            // sample analog channel and average over 8 samples
            for (i = 0; i < 8; i++) {
                analog_ch += read_analog_input(ch_index);
            }
            analog_ch /= 8;

            // convert raw AD value to mV compensating for the voltage divider on the analog inputs (Rtop = 1k and Rbottom = 1k8).
            // Scaling is used.
            // (((V_in_max * 256) / 1023) * analog) / 256
            // ((((Vref[mV] * ((Rt+Rb) / Rb)) * 256) / 1023) * analog) / 256
            // ((((Vref[mV] * 1.55556) * 256) / 1023) * analog) / 256
            // (((Vref[mV] * 398) / 1023) * analog) / 256
            analog_ch *= 1285;
            analog_ch /= 256;

            // write analog channel value
            if (!buffer_write_uint32(write_buffer, analog_ch)) return AER_REQ_RSP_TOO_LARGE;

            return AER_REQ_RESPONSE_READY;
        }

        case 7:
        {
            //  <query name="pwm" description="PWM Set" id="7">
            //    <request>
            //	  <parameter name="pwm_pin" type="uint8"/>
            //	  <parameter name="pwm_val" type="uint8"/>
            //    </request>
            //    <response>
            //      <parameter name="pwm_status" type="uint8"/>
            //    </response>
            //  </query>

            uint8_t pwm_pin;
            uint8_t pwm_val;
            uint8_t pwm_status;

            if (!buffer_read_uint8(read_buffer, &pwm_pin)) return AER_REQ_TOO_SMALL;
            if (!buffer_read_uint8(read_buffer, &pwm_val)) return AER_REQ_TOO_SMALL;

            pwm_status = set_pwm(pwm_pin, true, pwm_val);

            if (!buffer_write_uint8(write_buffer, pwm_status)) return AER_REQ_RSP_TOO_LARGE;

            return AER_REQ_RESPONSE_READY;
        }

        case 8:
        {
            //  <query name="analog_raw" description="Raw analog inputs" id="8">
            //    <request>
            //	  <parameter name="ch_index" type="uint8"/>
            //    </request>
            //    <response>
            //      <parameter name="analog_ch" type="uint32"/>
            //    </response>
            //  </query>

            uint8_t ch_index;
            uint32_t analog_ch = 0;
            uint8_t i;

            // read analog channel number
            if (!buffer_read_uint8(read_buffer, &ch_index)) return AER_REQ_TOO_SMALL;

            // sample analog channel and average over 8 samples
            for (i = 0; i < 8; i++) {
                analog_ch += read_analog_input(ch_index);
            }
            analog_ch /= 8;

            // write analog channel value
            if (!buffer_write_uint32(write_buffer, analog_ch)) return AER_REQ_RSP_TOO_LARGE;

            return AER_REQ_RESPONSE_READY;
        }
        case 9:
        {
              //<query name="alarm" description="~ Alarm Management ~" id="9">
              //  <request>
              //    <parameter name="turned" type="uint8"/>
              //  </request>
              //  <response>
              //    <parameter name="response" type="uint8"/>
              //  </response>
              //</query>

            uint32_t turned;
            uint32_t response;

            buffer_read_uint32(read_buffer, &turned);

            // motion sensor turned on
            if(turned == 1)
            {
                TURNED = 1;
                response = DETECTION;
                buffer_write_uint32(write_buffer, response);
            }
            // motion sensor turned off
            if(turned == 0)
            {
                TURNED = 0;
                response = DETECTION; // motion detection turned off
                buffer_write_uint32(write_buffer, response);
            }

            return AER_REQ_RESPONSE_READY;
        }

#if ACL_DEMO == 1
        case 1000: // add user
        {
            //  <query name="acl_add" description="Add user to ACL" id="1000">
            //    <request>
            //	  <parameter name="clientId" type="raw"/>
            //    </request>
            //    <response>
            //      <parameter name="success" type="uint8"/>
            //    </response>
            //  </query>

            buffer_t clientIdBuffer;
            bool userAdded;

            // get name of new user
            if (!buffer_read_raw_nc(read_buffer, &clientIdBuffer)) return AER_REQ_TOO_SMALL;
            if (clientIdBuffer.size > ACL_MAXIMUM_USERNAME_LENGTH) {
                return AER_REQ_TOO_LARGE;
            }
            memcpy(currentClientId, (const void*) clientIdBuffer.data, clientIdBuffer.size); // copy clientId
            currentClientId[clientIdBuffer.size] = 0; // add zero termination to make it a proper C string

            // add user
            userAdded = acl_add(currentClientId); // add user to ACL

            // return whether user was added or not
            if (!buffer_write_uint8(write_buffer, userAdded)) return AER_REQ_RSP_TOO_LARGE;

            return AER_REQ_RESPONSE_READY;
        }
            break;

        case 1001: // remove user
        {
            //  <query name="acl_remove" description="Remove user from ACL" id="1001">
            //    <request>
            //	  <parameter name="clientId" type="raw"/>
            //    </request>
            //    <response>
            //      <parameter name="success" type="uint8"/>
            //    </response>
            //  </query>

            buffer_t clientIdBuffer;
            bool userRemoved;

            // get name of new user
            if (!buffer_read_raw_nc(read_buffer, &clientIdBuffer)) return AER_REQ_TOO_SMALL;
            if (clientIdBuffer.size > ACL_MAXIMUM_USERNAME_LENGTH) {
                return AER_REQ_TOO_LARGE;
            }
            memcpy(currentClientId, (const void*) clientIdBuffer.data, clientIdBuffer.size); // copy clientId
            currentClientId[clientIdBuffer.size] = 0; // add zero termination to make it a proper C string

            // add user
            userRemoved = acl_remove(currentClientId); // remove user from ACL

            // return whether user was removed or not
            if (!buffer_write_uint8(write_buffer, userRemoved)) return AER_REQ_RSP_TOO_LARGE;

            return AER_REQ_RESPONSE_READY;
        }
            break;
#endif


//        case 100:
//        {
//            //  <query name="set_led" description="Set LED state and return the new state" id="100">
//            //    <request>
//            //		<parameter name="state" type="uint8"/>
//            //	</request>
//            //    <response>
//            //		<parameter name="state" type="uint8"/>
//            //    </response>
//            //  </query>
//
//            uint8_t state;
//
//            if (!buffer_read_uint8(read_buffer, &state)) return AER_REQ_TOO_SMALL;
//
//            LATDbits.LATD2 = state;
//
//            if (!buffer_write_uint8(write_buffer, state)) return AER_REQ_RSP_TOO_LARGE;
//
//            return AER_REQ_RESPONSE_READY;
//        }
//
//        case 101:
//        {
//            //  <query name="get_led" description="Get current LED state" id="101">
//            //    <request>
//            //	</request>
//            //    <response>
//            //		<parameter name="state" type="uint8"/>
//            //    </response>
//            //  </query>
//
//            uint8_t state = LATDbits.LATD2 != 0;
//
//            if (!buffer_write_uint8(write_buffer, state)) return AER_REQ_RSP_TOO_LARGE;
//
//            return AER_REQ_RESPONSE_READY;
//        }
//
//        case 102:
//        {
//            //  <query name="get_temperature" description="Get current temperature" id="102">
//            //    <request>
//            //    </request>
//            //    <response>
//            //      <parameter name="temperature" type="uint32"/>
//            //    </response>
//            //  </query>
//
//            uint32_t temperature = read_temperature();
//
//            if (!buffer_write_uint32(write_buffer, temperature)) return AER_REQ_RSP_TOO_LARGE;
//
//            return AER_REQ_RESPONSE_READY;
//        }
    }

    return AER_REQ_INV_QUERY_ID;
}

void setup(char** url) {
    // alternative HTML device driver URL
    //    static char urlBuffer[30];
    //    sprintf(urlBuffer, "file://c:/my_html_dd.zip");
    //    *url = urlBuffer;

#if ACL_DEMO == 1
    acl_initialize();
#endif
}


#if STREAM_DEMO == 1

enum {
    STREAM_STATE_IDLE,
    STREAM_STATE_CONNECTED,
    STREAM_STATE_CLOSING
} stream_state;

void streamLoop(void) {
    static nabto_stream_handle_t streamHandle;
    static uint8_t streamState = STREAM_STATE_IDLE;

    switch (streamState) {
        case STREAM_STATE_IDLE:
            // accept an incoming stream.
            if (nabto_stream_accept(&streamHandle)) {
                streamState = STREAM_STATE_CONNECTED;
            }
            break;

        case STREAM_STATE_CONNECTED:
        {
            uint8_t* buffer;
            int hint;

            // read from the stream, write back as much as can be written and acknowledge the amount of data actually written.
            size_t length = nabto_stream_read(&streamHandle, &buffer, &hint);
            if (length > 0) {
                length = nabto_stream_write(&streamHandle, buffer, length, &hint);
                if (length > 0) {
                    nabto_stream_ack(&streamHandle, buffer, length, &hint);
                }
            }

            // if anything bad happend close the stream.
            if (hint != 0) {
                streamState = STREAM_STATE_CLOSING;
            }
        }
            break;

        case STREAM_STATE_CLOSING:
            // attempt to close the stream.
            if (nabto_stream_close(&streamHandle)) {
                // when the stream is closed release the handle.

                nabto_stream_release(&streamHandle);
                streamState = STREAM_STATE_IDLE;
            }
            break;
    }
}
#endif

#if ACL_DEMO == 1

#pragma udata big_mem
static char aclList[ACL_MAXIMUM_USERS][ACL_MAXIMUM_USERNAME_LENGTH + 1]; // the ACL store. OBS this is in SRAM so the ACL is cleared on reset!
#pragma udata

void acl_initialize(void) {
    memset((void*) aclList, 0, sizeof (aclList)); // reset all entries
}

bool acl_lookup(const char* clientId) {
    uint8_t i;

    for (i = 0; i < ACL_MAXIMUM_USERS; i++) { // loop through all entries.
        if (strcmp(clientId, aclList[i]) == 0) { // check client id
            NABTO_DEBUGC(("ACL: Lookup succeeded for '%s' [%i].", clientId, (int) i));
            return true; // client id was in ACL.
        }
    }

    NABTO_DEBUGC(("ACL: Lookup failed for '%s'.", clientId));
    return false; // client id was not in ACL
}

bool acl_add(const char* clientId) {
    uint8_t i;

    if (strlen(clientId) > ACL_MAXIMUM_USERNAME_LENGTH) { // if client id is too big we can't store it.
        NABTO_DEBUGC(("ACL: Add failed due to length of client id."));
        return false; // invalid client id.
    }

    if (acl_lookup(clientId)) { // if client id is already in ACL just act like everything went well
        NABTO_DEBUGC(("ACL: Add '%s' (was already in ACL).", clientId));
        return true;
    }

    for (i = 0; i < ACL_MAXIMUM_USERS; i++) {
        if (aclList[i][0] == 0) { // find empty ACL entry.
            strcpy(aclList[i], clientId); // store client id.
            NABTO_DEBUGC(("ACL: Added '%s' [%i].", clientId, (int) i));
            return true; // user was added.
        }
    }

    NABTO_DEBUGC(("ACL: Add failed."));
    return false; // unable to find an empty entry.
}

bool acl_remove(const char* clientId) {
    uint8_t i;

    for (i = 0; i < ACL_MAXIMUM_USERS; i++) { // loop through all entries.
        if (strcmp(clientId, aclList[i]) == 0) { // if the client id was in the ACL.
            memset(aclList[i], 0, ACL_MAXIMUM_USERNAME_LENGTH); // reset entry.
            NABTO_DEBUGC(("ACL: Removed '%s' [%i].", clientId, (int) i));
            return true; // user was removed.
        }
    }

    NABTO_DEBUGC(("ACL: Remove failed."));
    return false; // user was not found.
}

#endif
